﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Transactions;
using System.Web;
using System.Web.Mvc;
using System.Web.Security;
using DotNetOpenAuth.AspNet;
using Microsoft.Web.WebPages.OAuth;
using WebMatrix.WebData;
using AdeToFood.Filters;
using AdeToFood.Models;

namespace AdeToFood.Controllers
{
	[Authorize]
	[InitializeSimpleMembership]
	public class AccountController : Controller
	{
		//
		// GET: /Account/Login

		[AllowAnonymous]
		public ActionResult Login( string returnUrl )
		{
			ViewBag.ReturnUrl = returnUrl;
			return View();
		}

		//
		// POST: /Account/Login

		[HttpPost]
		[AllowAnonymous]
		[ValidateAntiForgeryToken]
		public ActionResult Login( LoginModel model, string returnUrl )
		{
			if( ModelState.IsValid && WebSecurity.Login( model.UserName, model.Password, persistCookie: model.RememberMe ) )
			{
				return RedirectToLocal( returnUrl );
			}

			// If we got this far, something failed, redisplay form
			ModelState.AddModelError( "", "The user name or password provided is incorrect." );
			return View( model );
		}

		//
		// POST: /Account/LogOff

		[HttpPost]
		[ValidateAntiForgeryToken]
		public ActionResult LogOff()
		{
			WebSecurity.Logout();

			return RedirectToAction( "Index", "Home" );
		}

		//
		// GET: /Account/Register

		[AllowAnonymous]
		public ActionResult Register()
		{
			return View();
		}

		//
		// POST: /Account/Register

		[HttpPost]
		[AllowAnonymous]
		[ValidateAntiForgeryToken]
		public ActionResult Register( RegisterModel model )
		{
			if( ModelState.IsValid )
			{
				// Attempt to register the user
				try
				{
					WebSecurity.CreateUserAndAccount( model.UserName, model.Password );
					WebSecurity.Login( model.UserName, model.Password );
					return RedirectToAction( "Index", "Home" );
				}
				catch( MembershipCreateUserException e )
				{
					ModelState.AddModelError( "", ErrorCodeToString( e.StatusCode ) );
				}
			}

			// If we got this far, something failed, redisplay form
			return View( model );
		}

		//
		// POST: /Account/Disassociate

		[HttpPost]
		[ValidateAntiForgeryToken]
		public ActionResult Disassociate( string provider, string providerUserId )
		{
			string ownerAccount = OAuthWebSecurity.GetUserName( provider, providerUserId );
			ManageMessageId? message = null;

			// Only disassociate the account if the currently logged in user is the owner
			if( ownerAccount == User.Identity.Name )
			{
				// Use a transaction to prevent the user from deleting their last login credential
				using( var scope = new TransactionScope( TransactionScopeOption.Required, new TransactionOptions
				{
					IsolationLevel = IsolationLevel.Serializable
				} ) )
				{
					bool hasLocalAccount = OAuthWebSecurity.HasLocalAccount( WebSecurity.GetUserId( User.Identity.Name ) );
					if( hasLocalAccount || OAuthWebSecurity.GetAccountsFromUserName( User.Identity.Name ).Count > 1 )
					{
						OAuthWebSecurity.DeleteAccount( provider, providerUserId );
						scope.Complete();
						message = ManageMessageId.RemoveLoginSuccess;
					}
				}
			}

			return RedirectToAction( "Manage", new
			{
				Message = message
			} );
		}

		//
		// GET: /Account/Manage

		public ActionResult Manage( ManageMessageId? message )
		{
			ViewBag.StatusMessage =
				 message == ManageMessageId.ChangePasswordSuccess ? "Your password has been changed."
				 : message == ManageMessageId.SetPasswordSuccess ? "Your password has been set."
				 : message == ManageMessageId.RemoveLoginSuccess ? "The external login was removed."
				 : "";
			ViewBag.HasLocalPassword = OAuthWebSecurity.HasLocalAccount( WebSecurity.GetUserId( User.Identity.Name ) );
			ViewBag.ReturnUrl = Url.Action( "Manage" );
			return View();
		}

		//
		// POST: /Account/Manage

		[HttpPost]
		[ValidateAntiForgeryToken]
		public ActionResult Manage( LocalPasswordModel model )
		{
			bool hasLocalAccount = OAuthWebSecurity.HasLocalAccount( WebSecurity.GetUserId( User.Identity.Name ) );
			ViewBag.HasLocalPassword = hasLocalAccount;
			ViewBag.ReturnUrl = Url.Action( "Manage" );
			if( hasLocalAccount )
			{
				if( ModelState.IsValid )
				{
					// ChangePassword will throw an exception rather than return false in certain failure scenarios.
					bool changePasswordSucceeded;
					try
					{
						changePasswordSucceeded = WebSecurity.ChangePassword( User.Identity.Name, model.OldPassword, model.NewPassword );
					}
					catch( Exception )
					{
						changePasswordSucceeded = false;
					}

					if( changePasswordSucceeded )
					{
						return RedirectToAction( "Manage", new
						{
							Message = ManageMessageId.ChangePasswordSuccess
						} );
					}
					else
					{
						ModelState.AddModelError( "", "The current password is incorrect or the new password is invalid." );
					}
				}
			}
			else
			{
				// User does not have a local password so remove any validation errors caused by a missing
				// OldPassword field
				ModelState state = ModelState[ "OldPassword" ];
				if( state != null )
				{
					state.Errors.Clear();
				}

				if( ModelState.IsValid )
				{
					try
					{
						WebSecurity.CreateAccount( User.Identity.Name, model.NewPassword );
						return RedirectToAction( "Manage", new
						{
							Message = ManageMessageId.SetPasswordSuccess
						} );
					}
					catch( Exception e )
					{
						ModelState.AddModelError( "", e );
					}
				}
			}

			// If we got this far, something failed, redisplay form
			return View( model );
		}

		//
		// POST: /Account/ExternalLogin

		[HttpPost]
		[AllowAnonymous]
		[ValidateAntiForgeryToken]
		public ActionResult ExternalLogin( string provider, string returnUrl )
		{
			return new ExternalLoginResult( provider, Url.Action( "ExternalLoginCallback", new
			{
				ReturnUrl = returnUrl
			} ) );
		}

		//
		// GET: /Account/ExternalLoginCallback

		[AllowAnonymous]
		public ActionResult ExternalLoginCallback( string returnUrl )
		{
			AuthenticationResult result = OAuthWebSecurity.VerifyAuthentication( Url.Action( "ExternalLoginCallback", new
			{
				ReturnUrl = returnUrl
			} ) );
			if( !result.IsSuccessful )
			{
				return RedirectToAction( "ExternalLoginFailure" );
			}

			if( OAuthWebSecurity.Login( result.Provider, result.ProviderUserId, createPersistentCookie: false ) )
			{
				return RedirectToLocal( returnUrl );
			}

			if( User.Identity.IsAuthenticated )
			{
				// If the current user is logged in add the new account
				OAuthWebSecurity.CreateOrUpdateAccount( result.Provider, result.ProviderUserId, User.Identity.Name );
				return RedirectToLocal( returnUrl );
			}
			else
			{
				// User is new, ask for their desired membership name
				string loginData = OAuthWebSecurity.SerializeProviderUserId( result.Provider, result.ProviderUserId );
				ViewBag.ProviderDisplayName = OAuthWebSecurity.GetOAuthClientData( result.Provider ).DisplayName;
				ViewBag.ReturnUrl = returnUrl;
				return View( "ExternalLoginConfirmation", new RegisterExternalLoginModel
				{
					UserName = result.UserName,
					ExternalLoginData = loginData
				} );
			}
		}

		//
		// POST: /Account/ExternalLoginConfirmation

		[HttpPost]
		[AllowAnonymous]
		[ValidateAntiForgeryToken]
		public ActionResult ExternalLoginConfirmation( RegisterExternalLoginModel model, string returnUrl )
		{
			string provider = null;
			string providerUserId = null;

			if( User.Identity.IsAuthenticated || !OAuthWebSecurity.TryDeserializeProviderUserId( model.ExternalLoginData, out provider, out providerUserId ) )
			{
				return RedirectToAction( "Manage" );
			}

			if( ModelState.IsValid )
			{
				// Insert a new user into the database
				using( UsersContext db = new UsersContext() )
				{
					UserProfile user = db.UserProfiles.FirstOrDefault( u => u.UserName.ToLower() == model.UserName.ToLower() );
					// Check if user already exists
					if( user == null )
					{
						// Insert name into the profile table
						db.UserProfiles.Add( new UserProfile
						{
							UserName = model.UserName
						} );
						db.SaveChanges();

						OAuthWebSecurity.CreateOrUpdateAccount( provider, providerUserId, model.UserName );
						OAuthWebSecurity.Login( provider, providerUserId, createPersistentCookie: false );

						return RedirectToLocal( returnUrl );
					}
					else
					{
						ModelState.AddModelError( "UserName", "User name already exists. Please enter a different user name." );
					}
				}
			}

			ViewBag.ProviderDisplayName = OAuthWebSecurity.GetOAuthClientData( provider ).DisplayName;
			ViewBag.ReturnUrl = returnUrl;
			return View( model );
		}

		//
		// GET: /Account/ExternalLoginFailure

		[AllowAnonymous]
		public ActionResult ExternalLoginFailure()
		{
			return View();
		}

		[AllowAnonymous]
		[ChildActionOnly]
		public ActionResult ExternalLoginsList( string returnUrl )
		{
			ViewBag.ReturnUrl = returnUrl;
			return PartialView( "_ExternalLoginsListPartial", OAuthWebSecurity.RegisteredClientData );
		}

		[ChildActionOnly]
		public ActionResult RemoveExternalLogins()
		{
			ICollection<OAuthAccount> accounts = OAuthWebSecurity.GetAccountsFromUserName( User.Identity.Name );
			List<ExternalLogin> externalLogins = new List<ExternalLogin>();
			foreach( OAuthAccount account in accounts )
			{
				AuthenticationClientData clientData = OAuthWebSecurity.GetOAuthClientData( account.Provider );

				externalLogins.Add( new ExternalLogin
				{
					Provider = account.Provider,
					ProviderDisplayName = clientData.DisplayName,
					ProviderUserId = account.ProviderUserId,
				} );
			}

			ViewBag.ShowRemoveButton = externalLogins.Count > 1 || OAuthWebSecurity.HasLocalAccount( WebSecurity.GetUserId( User.Identity.Name ) );
			return PartialView( "_RemoveExternalLoginsPartial", externalLogins );
		}

		#region Helpers
		private ActionResult RedirectToLocal( string returnUrl )
		{
			if( Url.IsLocalUrl( returnUrl ) )
			{
				return Redirect( returnUrl );
			}
			else
			{
				return RedirectToAction( "Index", "Home" );
			}
		}

		public enum ManageMessageId
		{
			ChangePasswordSuccess,
			SetPasswordSuccess,
			RemoveLoginSuccess,
		}

		internal class ExternalLoginResult : ActionResult
		{
			public ExternalLoginResult( string provider, string returnUrl )
			{
				Provider = provider;
				ReturnUrl = returnUrl;
			}

			public string Provider
			{
				get;
				private set;
			}
			public string ReturnUrl
			{
				get;
				private set;
			}

			public override void ExecuteResult( ControllerContext context )
			{
				OAuthWebSecurity.RequestAuthentication( Provider, ReturnUrl );
			}
		}

		private static string ErrorCodeToString( MembershipCreateStatus createStatus )
		{
			// See http://go.microsoft.com/fwlink/?LinkID=177550 for
			// a full list of status codes.
			switch( createStatus )
			{
				case MembershipCreateStatus.DuplicateUserName:
				return "User name already exists. Please enter a different user name.";

				case MembershipCreateStatus.DuplicateEmail:
				return "A user name for that e-mail address already exists. Please enter a different e-mail address.";

				case MembershipCreateStatus.InvalidPassword:
				return "The password provided is invalid. Please enter a valid password value.";

				case MembershipCreateStatus.InvalidEmail:
				return "The e-mail address provided is invalid. Please check the value and try again.";

				case MembershipCreateStatus.InvalidAnswer:
				return "The password retrieval answer provided is invalid. Please check the value and try again.";

				case MembershipCreateStatus.InvalidQuestion:
				return "The password retrieval question provided is invalid. Please check the value and try again.";

				case MembershipCreateStatus.InvalidUserName:
				return "The user name provided is invalid. Please check the value and try again.";

				case MembershipCreateStatus.ProviderError:
				return "The authentication provider returned an error. Please verify your entry and try again. If the problem persists, please contact your system administrator.";

				case MembershipCreateStatus.UserRejected:
				return "The user creation request has been canceled. Please verify your entry and try again. If the problem persists, please contact your system administrator.";

				default:
				return "An unknown error occurred. Please verify your entry and try again. If the problem persists, please contact your system administrator.";
			}
		}
		#endregion
	}
}
